<?php

    /**
     * FTP - access to an FTP server.
     *
     * Usage
     * -----
     *
     * Opens an FTP connection to the specified host:
     *
     *      $ftp = new Core_IO_Ftp();
     *      $ftp->connect($host);
     *
     * Login with username and password
     *
     *      $ftp->login($username, $password);
     * 
     * Upload the file
     *
     *      $ftp->put($destination_file, $source_file, FTP_BINARY);
     * 
     * Close the FTP stream
     * 
     *      $ftp->close();
     *      // or simply unset($ftp);
     *
     * Ftp throws exception if operation failed. So you can simply do following:
     *
     *      try {
     *          $ftp = new Core_IO_Ftp();
     *          $ftp->connect($host);
     *          $ftp->login($username, $password);  
     *          $ftp->put($destination_file, $source_file, FTP_BINARY);
     * 
     *      } catch (Core_IO_Ftp_Exception $e) {
     *          echo 'Error: ', $e->getMessage();
     *      }
     * 
     * @package      Core
	 * @subpackage   IO
     *
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    class Core_IO_Ftp
    {

	////////////////////////////////////////////////////////////////////////////
	//                              Attributes                                //
	////////////////////////////////////////////////////////////////////////////

        const ASCII         = FTP_ASCII;
        const TEXT          = FTP_TEXT;
        const BINARY        = FTP_BINARY;
        const IMAGE         = FTP_IMAGE;
        const TIMEOUT_SEC   = FTP_TIMEOUT_SEC;
        const AUTOSEEK      = FTP_AUTOSEEK;
        const AUTORESUME    = FTP_AUTORESUME;
        const FAILED        = FTP_FAILED;
        const FINISHED      = FTP_FINISHED;
        const MOREDATA      = FTP_MOREDATA;

        /**
         * Aliases
         *
         * @var     array
         */
        private static $aliases = array(
            'sslconnect'    => 'ssl_connect',
            'getoption'     => 'get_option',
            'setoption'     => 'set_option',
            'nbcontinue'    => 'nb_continue',
            'nbfget'        => 'nb_fget',
            'nbfput'        => 'nb_fput',
            'nbget'         => 'nb_get',
            'nbput'         => 'nb_put',
        );

        /**
         * Resource
         *
         * @var resource
         */
        private $resource;

        /**
         * State
         *
         * @var array
         */
        private $state;

        /**
         * Error message
         *
         * @var string
         */
        private $errorMsg;

	////////////////////////////////////////////////////////////////////////////
	//                                  Methods                               //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Magic method (do not call directly).
         *
         * @param       string      $name
         * @param       mixed       $args
         * @return      mixed
         */
        public function __call($name, $args)
        {
            $name = strtolower($name);
            $silent = strncmp($name, 'try', 3) === 0;
            $func = $silent ? substr($name, 3) : $name;
            $func = 'ftp_' . (isset(self::$aliases[$func]) ? self::$aliases[$func] : $func);

            if (!function_exists($func)) {
                throw new Exception("Call to undefined method Core_IO_Ftp::$name().");
            }

            $this->errorMsg = NULL;
            set_error_handler(array($this, '_errorHandler'));

            if ($func === 'ftp_connect' || $func === 'ftp_ssl_connect') {
                $this->state = array($name => $args);
                $this->resource = call_user_func_array($func, $args);
                $res = NULL;

            } elseif (!is_resource($this->resource)) {
                restore_error_handler();
                throw new Core_IO_Ftp_Exception("Not connected to FTP server. Call connect() or ssl_connect() first.");

            } else {
                if ($func === 'ftp_login') {
                    $this->state['login'] = $args;
                }

                array_unshift($args, $this->resource);
                $res = call_user_func_array($func, $args);

                if ($func === 'ftp_chdir' || $func === 'ftp_cdup') {
                    $this->state['chdir'] = array(ftp_pwd($this->resource));
                }
            }

            restore_error_handler();
            if (!$silent && $this->errorMsg !== NULL) {
                if (ini_get('html_errors')) {
                    $this->errorMsg = html_entity_decode(strip_tags($this->errorMsg));
                }

                if (($a = strpos($this->errorMsg, ': ')) !== FALSE) {
                    $this->errorMsg = substr($this->errorMsg, $a + 2);
                }

                throw new Core_IO_Ftp_Exception($this->errorMsg);
            }

            return $res;
        }

        /**
         * Internal error handler. Do not call directly.
         *
         * @param       string      $code
         * @param       string      $message
         * @return      void
         */
        public function _errorHandler($code, $message)
        {
            $this->errorMsg = $message;
        }

        /**
         * Reconnects to FTP server.
         * 
         * @return      void
         */
        public function reconnect()
        {
            @ftp_close($this->resource);
            foreach ($this->state as $name => $args) {
                call_user_func_array(array($this, $name), $args);
            }
        }

        /**
         * Checks if file or directory exists.
         *
         * @param       string      $file
         * @return      bool
         */
        public function fileExists($file)
        {
            return is_array($this->nlist($file));
        }

        /**
         * Checks if directory exists.
         *
         * @param       string      $dir
         * @return      bool
         */
        public function isDir($dir)
        {
            $current = $this->pwd();
            try {
                $this->chdir($dir);
            } catch (Core_IO_Ftp_Exception $e) {
            }
            $this->chdir($current);
            return empty($e);
        }

        /**
         * Recursive creates directory.
         *
         * @param       string      $dir
         * @return      void
         */
        public function mkDirRecursive($dir)
        {
            $parts = explode('/', $dir);
            $path = '';
            while (!empty($parts)) {
                $path .= array_shift($parts);
                try {
                    if ($path !== '') $this->mkdir($path);
                } catch (Core_IO_Ftp_Exception $e) {
                    if (!$this->isDir($path)) {
                        throw new Core_IO_Ftp_Exception("Cannot create directory '$path'.");
                    }
                }
                $path .= '/';
            }
        }

    }